home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 748 / 748.xpi / components / greasemonkey.js < prev   
Text File  |  2010-02-11  |  18KB  |  556 lines

  1. const CLASSNAME = "GM_GreasemonkeyService";
  2. const CONTRACTID = "@greasemonkey.mozdev.org/greasemonkey-service;1";
  3. const CID = Components.ID("{77bf3650-1cd6-11da-8cd6-0800200c9a66}");
  4.  
  5. const Cc = Components.classes;
  6. const Ci = Components.interfaces;
  7.  
  8. const appSvc = Cc["@mozilla.org/appshell/appShellService;1"]
  9.                  .getService(Ci.nsIAppShellService);
  10.  
  11. const gmSvcFilename = Components.stack.filename;
  12.  
  13. const maxJSVersion = (function getMaxJSVersion() {
  14.   var appInfo = Components
  15.       .classes["@mozilla.org/xre/app-info;1"]
  16.       .getService(Components.interfaces.nsIXULAppInfo);
  17.   var versionChecker = Components
  18.       .classes["@mozilla.org/xpcom/version-comparator;1"]
  19.       .getService(Components.interfaces.nsIVersionComparator);
  20.  
  21.   // Firefox 3.5 and higher supports 1.8.
  22.   if (versionChecker.compare(appInfo.version, "3.5") >= 0) {
  23.     return "1.8";
  24.   }
  25.  
  26.   // Everything else supports 1.6.
  27.   return "1.6";
  28. })();
  29.  
  30. function alert(msg) {
  31.   Cc["@mozilla.org/embedcomp/prompt-service;1"]
  32.     .getService(Ci.nsIPromptService)
  33.     .alert(null, "Greasemonkey alert", msg);
  34. }
  35.  
  36. // Examines the stack to determine if an API should be callable.
  37. function GM_apiLeakCheck(apiName) {
  38.   var stack = Components.stack;
  39.  
  40.   do {
  41.     // Valid stack frames for GM api calls are: native and js when coming from
  42.     // chrome:// URLs and the greasemonkey.js component's file:// URL.
  43.     if (2 == stack.language) {
  44.       // NOTE: In FF 2.0.0.0, I saw that stack.filename can be null for JS/XPCOM
  45.       // services. This didn't happen in FF 2.0.0.11; I'm not sure when it
  46.       // changed.
  47.       if (stack.filename != null &&
  48.           stack.filename != gmSvcFilename &&
  49.           stack.filename.substr(0, 6) != "chrome") {
  50.         GM_logError(new Error("Greasemonkey access violation: unsafeWindow " +
  51.                     "cannot call " + apiName + "."));
  52.         return false;
  53.       }
  54.     }
  55.  
  56.     stack = stack.caller;
  57.   } while (stack);
  58.  
  59.   return true;
  60. }
  61.  
  62. var greasemonkeyService = {
  63.   _config: null,
  64.   get config() {
  65.     if (!this._config)
  66.       this._config = new Config();
  67.     return this._config;
  68.   },
  69.   browserWindows: [],
  70.  
  71.  
  72.   // nsISupports
  73.   QueryInterface: function(aIID) {
  74.     if (!aIID.equals(Ci.nsIObserver) &&
  75.         !aIID.equals(Ci.nsISupports) &&
  76.         !aIID.equals(Ci.nsISupportsWeakReference) &&
  77.         !aIID.equals(Ci.gmIGreasemonkeyService) &&
  78.         !aIID.equals(Ci.nsIWindowMediatorListener) &&
  79.         !aIID.equals(Ci.nsIContentPolicy)) {
  80.       throw Components.results.NS_ERROR_NO_INTERFACE;
  81.     }
  82.  
  83.     return this;
  84.   },
  85.  
  86.  
  87.   // nsIObserver
  88.   observe: function(aSubject, aTopic, aData) {
  89.     if (aTopic == "app-startup") {
  90.       this.startup();
  91.     }
  92.   },
  93.  
  94.  
  95.   // gmIGreasemonkeyService
  96.   registerBrowser: function(browserWin) {
  97.     var existing;
  98.  
  99.     for (var i = 0; existing = this.browserWindows[i]; i++) {
  100.       if (existing == browserWin) {
  101.         // NOTE: Unlocalised strings
  102.         throw new Error("Browser window has already been registered.");
  103.       }
  104.     }
  105.  
  106.     this.browserWindows.push(browserWin);
  107.   },
  108.  
  109.   unregisterBrowser: function(browserWin) {
  110.    var existing;
  111.  
  112.     for (var i = 0; existing = this.browserWindows[i]; i++) {
  113.       if (existing == browserWin) {
  114.         this.browserWindows.splice(i, 1);
  115.         return;
  116.       }
  117.     }
  118.  
  119.     throw new Error("Browser window is not registered.");
  120.   },
  121.  
  122.   domContentLoaded: function(wrappedContentWin, chromeWin) {
  123.     var unsafeWin = wrappedContentWin.wrappedJSObject;
  124.     var unsafeLoc = new XPCNativeWrapper(unsafeWin, "location").location;
  125.     var href = new XPCNativeWrapper(unsafeLoc, "href").href;
  126.     var scripts = this.initScripts(href);
  127.  
  128.     if (scripts.length > 0) {
  129.       this.injectScripts(scripts, href, unsafeWin, chromeWin);
  130.     }
  131.   },
  132.  
  133.  
  134.   startup: function() {
  135.     var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
  136.       .getService(Ci.mozIJSSubScriptLoader);
  137.     loader.loadSubScript("chrome://global/content/XPCNativeWrapper.js");
  138.     loader.loadSubScript("chrome://greasemonkey/content/prefmanager.js");
  139.     loader.loadSubScript("chrome://greasemonkey/content/utils.js");
  140.     loader.loadSubScript("chrome://greasemonkey/content/config.js");
  141.     loader.loadSubScript("chrome://greasemonkey/content/convert2RegExp.js");
  142.     loader.loadSubScript("chrome://greasemonkey/content/miscapis.js");
  143.     loader.loadSubScript("chrome://greasemonkey/content/xmlhttprequester.js");
  144.     //loggify(this, "GM_GreasemonkeyService");
  145.   },
  146.  
  147.   shouldLoad: function(ct, cl, org, ctx, mt, ext) {
  148.     var ret = Ci.nsIContentPolicy.ACCEPT;
  149.  
  150.     // block content detection of greasemonkey by denying GM
  151.     // chrome content, unless loaded from chrome
  152.     if (org && org.scheme != "chrome" && cl.scheme == "chrome" &&
  153.         cl.host == "greasemonkey") {
  154.       return Ci.nsIContentPolicy.REJECT_SERVER;
  155.     }
  156.  
  157.     // don't intercept anything when GM is not enabled
  158.     if (!GM_getEnabled()) {
  159.       return ret;
  160.     }
  161.  
  162.     // don't interrupt the view-source: scheme
  163.     // (triggered if the link in the error console is clicked)
  164.     if ("view-source" == cl.scheme) {
  165.       return ret;
  166.     }
  167.  
  168.     if (ct == Ci.nsIContentPolicy.TYPE_DOCUMENT &&
  169.         cl.spec.match(/\.user\.js$/)) {
  170.  
  171.       dump("shouldload: " + cl.spec + "\n");
  172.       dump("ignorescript: " + this.ignoreNextScript_ + "\n");
  173.  
  174.       if (!this.ignoreNextScript_) {
  175.         if (!this.isTempScript(cl)) {
  176.           var winWat = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  177.             .getService(Ci.nsIWindowWatcher);
  178.  
  179.           if (winWat.activeWindow && winWat.activeWindow.GM_BrowserUI) {
  180.             winWat.activeWindow.GM_BrowserUI.startInstallScript(cl);
  181.             ret = Ci.nsIContentPolicy.REJECT_REQUEST;
  182.           }
  183.         }
  184.       }
  185.     }
  186.  
  187.     this.ignoreNextScript_ = false;
  188.     return ret;
  189.   },
  190.  
  191.   shouldProcess: function(ct, cl, org, ctx, mt, ext) {
  192.     return Ci.nsIContentPolicy.ACCEPT;
  193.   },
  194.  
  195.   ignoreNextScript: function() {
  196.     dump("ignoring next script...\n");
  197.     this.ignoreNextScript_ = true;
  198.   },
  199.  
  200.   isTempScript: function(uri) {
  201.     if (uri.scheme != "file") {
  202.       return false;
  203.     }
  204.  
  205.     var fph = Components.classes["@mozilla.org/network/protocol;1?name=file"]
  206.     .getService(Ci.nsIFileProtocolHandler);
  207.  
  208.     var file = fph.getFileFromURLSpec(uri.spec);
  209.     var tmpDir = Components.classes["@mozilla.org/file/directory_service;1"]
  210.     .getService(Components.interfaces.nsIProperties)
  211.     .get("TmpD", Components.interfaces.nsILocalFile);
  212.  
  213.     return file.parent.equals(tmpDir) && file.leafName != "newscript.user.js";
  214.   },
  215.  
  216.   initScripts: function(url) {
  217.     function testMatch(script) {
  218.       return script.enabled && script.matchesURL(url);
  219.     }
  220.  
  221.     return GM_getConfig().getMatchingScripts(testMatch);
  222.   },
  223.  
  224.   injectScripts: function(scripts, url, unsafeContentWin, chromeWin) {
  225.     var sandbox;
  226.     var script;
  227.     var logger;
  228.     var console;
  229.     var storage;
  230.     var xmlhttpRequester;
  231.     var resources;
  232.     var safeWin = new XPCNativeWrapper(unsafeContentWin);
  233.     var safeDoc = safeWin.document;
  234.  
  235.     // detect and grab reference to firebug console and context, if it exists
  236.     var firebugConsole = this.getFirebugConsole(unsafeContentWin, chromeWin);
  237.  
  238.     for (var i = 0; script = scripts[i]; i++) {
  239.       sandbox = new Components.utils.Sandbox(safeWin);
  240.  
  241.       logger = new GM_ScriptLogger(script);
  242.  
  243.       console = firebugConsole ? firebugConsole : new GM_console(script);
  244.  
  245.       storage = new GM_ScriptStorage(script);
  246.       xmlhttpRequester = new GM_xmlhttpRequester(unsafeContentWin,
  247.                                                  appSvc.hiddenDOMWindow);
  248.       resources = new GM_Resources(script);
  249.  
  250.       sandbox.window = safeWin;
  251.       sandbox.document = sandbox.window.document;
  252.       sandbox.unsafeWindow = unsafeContentWin;
  253.  
  254.       // hack XPathResult since that is so commonly used
  255.       sandbox.XPathResult = Ci.nsIDOMXPathResult;
  256.  
  257.       // add our own APIs
  258.       sandbox.GM_addStyle = function(css) { GM_addStyle(safeDoc, css) };
  259.       sandbox.GM_log = GM_hitch(logger, "log");
  260.       sandbox.console = console;
  261.       sandbox.GM_setValue = GM_hitch(storage, "setValue");
  262.       sandbox.GM_getValue = GM_hitch(storage, "getValue");
  263.       sandbox.GM_deleteValue = GM_hitch(storage, "deleteValue");
  264.       sandbox.GM_listValues = GM_hitch(storage, "listValues");
  265.       sandbox.GM_getResourceURL = GM_hitch(resources, "getResourceURL");
  266.       sandbox.GM_getResourceText = GM_hitch(resources, "getResourceText");
  267.       sandbox.GM_openInTab = GM_hitch(this, "openInTab", safeWin, chromeWin);
  268.       sandbox.GM_xmlhttpRequest = GM_hitch(xmlhttpRequester,
  269.                                            "contentStartRequest");
  270.       sandbox.GM_registerMenuCommand = GM_hitch(this,
  271.                                                 "registerMenuCommand",
  272.                                                 unsafeContentWin);
  273.  
  274.       sandbox.__proto__ = safeWin;
  275.  
  276.       var contents = script.textContent;
  277.  
  278.       var requires = [];
  279.       var offsets = [];
  280.       var offset = 0;
  281.  
  282.       script.requires.forEach(function(req){
  283.         var contents = req.textContent;
  284.         var lineCount = contents.split("\n").length;
  285.         requires.push(contents);
  286.         offset += lineCount;
  287.         offsets.push(offset);
  288.       });
  289.       script.offsets = offsets;
  290.  
  291.       var scriptSrc = "\n" + // error line-number calculations depend on these
  292.                          requires.join("\n") +
  293.                          "\n" +
  294.                          contents +
  295.                          "\n";
  296.       if (!script.unwrap)
  297.         scriptSrc = "(function(){"+ scriptSrc +"})()";
  298.       if (!this.evalInSandbox(scriptSrc, url, sandbox, script) && script.unwrap)
  299.         this.evalInSandbox("(function(){"+ scriptSrc +"})()",
  300.                            url, sandbox, script); // wrap anyway on early return
  301.     }
  302.   },
  303.  
  304.   registerMenuCommand: function(unsafeContentWin, commandName, commandFunc,
  305.                                 accelKey, accelModifiers, accessKey) {
  306.     if (!GM_apiLeakCheck("GM_registerMenuCommand")) {
  307.       return;
  308.     }
  309.  
  310.     var command = {name: commandName,
  311.                    accelKey: accelKey,
  312.                    accelModifiers: accelModifiers,
  313.                    accessKey: accessKey,
  314.                    doCommand: commandFunc,
  315.                    window: unsafeContentWin };
  316.  
  317.     for (var i = 0; i < this.browserWindows.length; i++) {
  318.       this.browserWindows[i].registerMenuCommand(command);
  319.     }
  320.   },
  321.  
  322.   openInTab: function(safeContentWin, chromeWin, url) {
  323.     if (!GM_apiLeakCheck("GM_openInTab")) {
  324.       return undefined;
  325.     }
  326.  
  327.     var info = Cc["@mozilla.org/xre/app-info;1"]
  328.       .getService(Components.interfaces.nsIXULAppInfo);
  329.     if (parseFloat(info.version, 10) < 3.0) {
  330.       // Pre FF 3.0 wants the URL as the second argument.
  331.       var newTab = chromeWin.openNewTabWith(
  332.         url, safeContentWin.document.location.href, null, null, null, null);
  333.     } else {
  334.       // Post FF 3.0 wants the document as the second argument.
  335.       var newTab = chromeWin.openNewTabWith(
  336.         url, safeContentWin.document, null, null, null, null);
  337.     }
  338.  
  339.     // Source:
  340.     // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4448
  341.     var newWindow = chromeWin.gBrowser
  342.       .getBrowserForTab(newTab)
  343.       .docShell
  344.       .QueryInterface(Ci.nsIInterfaceRequestor)
  345.       .getInterface(Ci.nsIDOMWindow);
  346.     return newWindow;
  347.   },
  348.  
  349.   evalInSandbox: function(code, codebase, sandbox, script) {
  350.     if (!(Components.utils && Components.utils.Sandbox)) {
  351.       var e = new Error("Could not create sandbox.");
  352.       GM_logError(e, 0, e.fileName, e.lineNumber);
  353.       return true;
  354.     }
  355.     try {
  356.       // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=307984
  357.       var lineFinder = new Error();
  358.       Components.utils.evalInSandbox(code, sandbox, maxJSVersion);
  359.     } catch (e) { // catches errors while running the script code
  360.       try {
  361.         if (e && "return not in function" == e.message)
  362.           return false; // means this script depends on the function enclosure
  363.  
  364.         // try to find the line of the actual error line
  365.         var line = e && e.lineNumber;
  366.         if (4294967295 == line) {
  367.           // Line number is reported as max int in edge cases.  Sometimes
  368.           // the right one is in the "location", instead.  Look there.
  369.           if (e.location && e.location.lineNumber) {
  370.             line = e.location.lineNumber;
  371.           } else {
  372.             // Reporting maxint is useless, if we couldn't find it in location
  373.             // either, forget it.  A value of 0 isn't shown in the console.
  374.             line = 0;
  375.           }
  376.         }
  377.  
  378.         if (line) {
  379.           var err = this.findError(script, line - lineFinder.lineNumber - 1);
  380.           GM_logError(
  381.             e, // error obj
  382.             0, // 0 = error (1 = warning)
  383.             err.uri,
  384.             err.lineNumber
  385.           );
  386.         } else {
  387.           GM_logError(
  388.             e, // error obj
  389.             0, // 0 = error (1 = warning)
  390.             script.fileURL,
  391.             0
  392.           );
  393.         }
  394.       } catch (e) { // catches errors we cause trying to inform the user
  395.         // Do nothing. More importantly: don't stop script incovation sequence.
  396.       }
  397.     }
  398.     return true; // did not need a (function() {...})() enclosure.
  399.   },
  400.  
  401.   findError: function(script, lineNumber){
  402.     var start = 0;
  403.     var end = 1;
  404.  
  405.     for (var i = 0; i < script.offsets.length; i++) {
  406.       end = script.offsets[i];
  407.       if (lineNumber < end) {
  408.         return {
  409.           uri: script.requires[i].fileURL,
  410.           lineNumber: (lineNumber - start)
  411.         };
  412.       }
  413.       start = end;
  414.     }
  415.  
  416.     return {
  417.       uri: script.fileURL,
  418.       lineNumber: (lineNumber - end)
  419.     };
  420.   },
  421.  
  422.   getFirebugConsole: function(unsafeContentWin, chromeWin) {
  423.     // If we can't find this object, there's no chance the rest of this
  424.     // function will work.
  425.     if ('undefined'==typeof chromeWin.Firebug) return null;
  426.  
  427.     try {
  428.       chromeWin = chromeWin.top;
  429.       var fbVersion = parseFloat(chromeWin.Firebug.version, 10);
  430.       var fbConsole = chromeWin.Firebug.Console;
  431.       var fbContext = chromeWin.TabWatcher &&
  432.         chromeWin.TabWatcher.getContextByWindow(unsafeContentWin);
  433.  
  434.       // Firebug 1.4 will give no context, when disabled for the current site.
  435.       // We can't run that way.
  436.       if ('undefined'==typeof fbContext) {
  437.         return null;
  438.       }
  439.  
  440.       function findActiveContext() {
  441.         for (var i=0; i<fbContext.activeConsoleHandlers.length; i++) {
  442.           if (fbContext.activeConsoleHandlers[i].window == unsafeContentWin) {
  443.             return fbContext.activeConsoleHandlers[i];
  444.           }
  445.         }
  446.         return null;
  447.       }
  448.  
  449.       try {
  450.         if (!fbConsole.isEnabled(fbContext)) return null;
  451.       } catch (e) {
  452.         // FB 1.1 can't be enabled/disabled.  Function to check doesn't exist.
  453.         // Silently ignore.
  454.       }
  455.  
  456.       if (fbVersion < 1.2) {
  457.         return new chromeWin.FirebugConsole(fbContext, unsafeContentWin);
  458.       } else if (1.2 == fbVersion) {
  459.         var safeWin = new XPCNativeWrapper(unsafeContentWin);
  460.  
  461.         if (fbContext.consoleHandler) {
  462.           for (var i = 0; i < fbContext.consoleHandler.length; i++) {
  463.             if (fbContext.consoleHandler[i].window == safeWin) {
  464.               return fbContext.consoleHandler[i].handler;
  465.             }
  466.           }
  467.         }
  468.  
  469.         var dummyElm = safeWin.document.createElement("div");
  470.         dummyElm.setAttribute("id", "_firebugConsole");
  471.         safeWin.document.documentElement.appendChild(dummyElm);
  472.         chromeWin.Firebug.Console.injector.addConsoleListener(fbContext, safeWin);
  473.         dummyElm.parentNode.removeChild(dummyElm);
  474.  
  475.         return fbContext.consoleHandler.pop().handler;
  476.       } else if (1.3 == fbVersion || 1.4 == fbVersion || 1.5 == fbVersion) {
  477.         fbConsole.injector.attachIfNeeded(fbContext, unsafeContentWin);
  478.         return findActiveContext();
  479.       }
  480.     } catch (e) {
  481.       dump('Greasemonkey getFirebugConsole() error:\n'+uneval(e)+'\n');
  482.     }
  483.  
  484.       return null;
  485.   }
  486. };
  487.  
  488. greasemonkeyService.wrappedJSObject = greasemonkeyService;
  489.  
  490.  
  491.  
  492. /**
  493.  * XPCOM Registration goop
  494.  */
  495. var Module = new Object();
  496.  
  497. Module.registerSelf = function(compMgr, fileSpec, location, type) {
  498.   compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  499.   compMgr.registerFactoryLocation(CID,
  500.                                   CLASSNAME,
  501.                                   CONTRACTID,
  502.                                   fileSpec,
  503.                                   location,
  504.                                   type);
  505.  
  506.   var catMgr = Cc["@mozilla.org/categorymanager;1"]
  507.                  .getService(Ci.nsICategoryManager);
  508.  
  509.   catMgr.addCategoryEntry("app-startup",
  510.                           CLASSNAME,
  511.                           CONTRACTID,
  512.                           true,
  513.                           true);
  514.  
  515.   catMgr.addCategoryEntry("content-policy",
  516.                           CONTRACTID,
  517.                           CONTRACTID,
  518.                           true,
  519.                           true);
  520. };
  521.  
  522. Module.getClassObject = function(compMgr, cid, iid) {
  523.   if (!cid.equals(CID)) {
  524.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  525.   }
  526.  
  527.   if (!iid.equals(Ci.nsIFactory)) {
  528.     throw Components.results.NS_ERROR_NO_INTERFACE;
  529.   }
  530.  
  531.   return Factory;
  532. };
  533.  
  534. Module.canUnload = function(compMgr) {
  535.   return true;
  536. };
  537.  
  538.  
  539. var Factory = new Object();
  540.  
  541. Factory.createInstance = function(outer, iid) {
  542.   if (outer != null) {
  543.     throw Components.results.NS_ERROR_NO_AGGREGATION;
  544.   }
  545.  
  546.   return greasemonkeyService;
  547. };
  548.  
  549.  
  550. function NSGetModule(compMgr, fileSpec) {
  551.   return Module;
  552. }
  553.  
  554. //loggify(Module, "greasemonkeyService:Module");
  555. //loggify(Factory, "greasemonkeyService:Factory");
  556.